#include "models.hpp"

#include "commandManager.hpp"
#include "macroCommand.hpp"

#include "addObjCR.hpp"
#include "changeObjCR.hpp"
#include "deleObjCR.hpp"

#include <entt/entt.hpp>
#include "assert.h"

#include "qlib/all.h"

#include "events.hpp"

#include "qbe/src/layout/layout_cmd.hpp"

#include "addObjCRs.hpp"
#include "changeObjCRs.hpp"
#include "deleObjCRs.hpp"
#include "tools/tools_interface.h"

namespace qbe::undoRedoT {

UndoRedoModule::UndoRedoModule(entt::registry* _reg, ecs::Query _query, ecs::EventWriter<Event> writer)
    : reg(_reg), query(_query), event_writer(writer) {}

void UndoRedoModule::switchCell(std::shared_ptr<qlib::Cell> cell) {
  CommandManager* comm = CommandManager::Instance();

  auto it = cellToUndoRedoMap.find(cell);
  if (it == cellToUndoRedoMap.end()) {
    auto index = comm->createUndoRedoPool();
    cellToUndoRedoMap[cell] = index;
  }

  auto index = cellToUndoRedoMap[cell];
  comm->switchUndoRedo(index);
}

void UndoRedoModule::addCommand(std::shared_ptr<qlib::Cell> cell, BaseCommand* command) {
  switchCell(cell);

  CommandManager* comm = CommandManager::Instance();
  comm->CallCommand(command);
}
void UndoRedoModule::resetOldData() {
  for (auto i : change_receivers) {
    i->resetOldData();
  }
}

void UndoRedoModule::start(std::shared_ptr<qlib::Cell> _cell) {
  /*if (isStart) {
    assert(0);
  }*/

  isStart = true;

  macr_command = new MacroCommand;

  cell = _cell;
  add_entitys.clear();
  delete_entitys.clear();
  change_entitys.clear();
  change_receivers.clear();
}

template <typename T, typename... Args>
void add_impl(ecs::Query query, entt::entity e, entt::registry* reg, MacroCommand* macr_command, ecs::EventWriter<Event>& writer,
              layout::LayoutCmd* _cmd, bool use_cmd = true) {
  auto ptr2 = reg->try_get<std::shared_ptr<T>>(e);

  if (ptr2 != nullptr) {
    auto library = query.get_resource<qlib::Library::s_ptr>();

    AddObjCR<T>* cr = new AddObjCR<T>;
    cr->Prepare(library, reg, e, writer, _cmd, use_cmd);

    Command* command = new Command;
    command->SetReceiver(cr);

    macr_command->AddCommand(command);

    if constexpr (std::is_same_v<T, qlib::Reference>) {
      macr_command->addTag("Add Reference");
    }

    return;
  }

  if constexpr (sizeof...(Args) > 0) {
    add_impl<Args...>(query, e, reg, macr_command, writer, _cmd);
  } else {
    assert(0);
  }
}

void UndoRedoModule::add(entt::entity e) {
  add_entitys.push_back(e);
  auto& cmd = query.get_resource<layout::LayoutCmd>();
  add_impl<QLIB_CELL_ELEMENT_TYPE>(query, e, reg, macr_command, event_writer, &cmd);
}

template <typename T, typename... Args>
void change_impl(entt::entity e, entt::registry* reg, MacroCommand* macr_command, ecs::EventWriter<Event>& writer, layout::LayoutCmd* _cmd,
                 std::vector<BaseCommandReceiver*>& change_receivers, bool use_cmd = true) {
  auto ptr2 = reg->try_get<std::shared_ptr<T>>(e);

  if (ptr2 != nullptr) {
    ChangeObjCR<T>* cr = new ChangeObjCR<T>;
    cr->Prepare(reg, e, writer, _cmd, use_cmd);
    change_receivers.push_back(cr);

    Command* command = new Command;
    command->SetReceiver(cr);

    macr_command->AddCommand(command);

    if constexpr (std::is_same_v<T, qlib::Reference>) {
      macr_command->addTag("Change Reference");
    }

    return;
  }

  if constexpr (sizeof...(Args) > 0) {
    change_impl<Args...>(e, reg, macr_command, writer, _cmd, change_receivers);
  } else {
    return;
    // assert(0);
  }
}

void UndoRedoModule::change(entt::entity e) {
  change_entitys.push_back(e);
  auto& cmd = query.get_resource<layout::LayoutCmd>();
  change_impl<QLIB_CELL_ELEMENT_TYPE>(e, reg, macr_command, event_writer, &cmd, change_receivers);
}

void UndoRedoModule::change(std::vector<entt::entity> es) {
  change_entitys.insert(change_entitys.end(), es.begin(), es.end());
  auto& cmd = query.get_resource<layout::LayoutCmd>();

  for (auto e : es) {
    change_impl<QLIB_CELL_ELEMENT_TYPE>(e, reg, macr_command, event_writer, &cmd, change_receivers, false);
  }

  ChangeObjCRs* cr = new ChangeObjCRs;
  cr->Prepare(reg, es, event_writer, &cmd);

  Command* command = new Command;
  command->SetReceiver(cr);

  macr_command->AddCommand(command);
}

template <typename T, typename... Args>
void dele_impl(ecs::Query query, entt::entity e, entt::registry* reg, MacroCommand* macr_command, ecs::EventWriter<Event>& writer,
               layout::LayoutCmd* _cmd, bool use_cmd = true) {
  auto ptr2 = reg->try_get<std::shared_ptr<T>>(e);

  if (ptr2 != nullptr) {
    auto library = query.get_resource<qlib::Library::s_ptr>();

    DeleObjCR<T>* cr = new DeleObjCR<T>;
    cr->Prepare(library, reg, e, writer, _cmd, use_cmd);

    Command* command = new Command;
    command->SetReceiver(cr);

    macr_command->AddCommand(command);

    if constexpr (std::is_same_v<T, qlib::Reference>) {
      macr_command->addTag("Delete Reference");
    }

    return;
  }

  if constexpr (sizeof...(Args) > 0) {
    dele_impl<Args...>(query, e, reg, macr_command, writer, _cmd);
  } else {
    // assert(0);
  }
}

void UndoRedoModule::dele(entt::entity e) {
  delete_entitys.push_back(e);
  auto& cmd = query.get_resource<layout::LayoutCmd>();
  dele_impl<QLIB_CELL_ELEMENT_TYPE>(query, e, reg, macr_command, event_writer, &cmd);
}

void UndoRedoModule::end() {
  assert(isStart);

  switchCell(cell);

  CommandManager* comm = CommandManager::Instance();
  comm->CallCommand(macr_command);

  isStart = false;
  macr_command = nullptr;
  auto& interface_objcet = query.get_resource<tools::InterfaceObjcet>();
  interface_objcet.recordUndoRedoDataEnd(add_entitys, delete_entitys, change_entitys);
  change_receivers.clear();
}

void UndoRedoModule::add(std::vector<entt::entity> es) {
  add_entitys.insert(add_entitys.end(), es.begin(), es.end());
  for (auto e : es) {
    add(e);
  }
  // auto& cmd = query.get_resource<layout::LayoutCmd>();
  // for (auto e : es) {
  //   add_impl<QLIB_CELL_ELEMENT_TYPE>(e, reg, macr_command, event_writer, &cmd, false);
  // }

  // AddObjCRs* cr = new AddObjCRs;
  // cr->Prepare(reg, es, event_writer, &cmd);

  // Command* command = new Command;
  // command->SetReceiver(cr);

  // macr_command->AddCommand(command);
}

void UndoRedoModule::dele(std::vector<entt::entity> es) {
  delete_entitys.insert(delete_entitys.end(), es.begin(), es.end());
  for (auto e : es) {
    dele(e);
  }
  // auto& cmd = query.get_resource<layout::LayoutCmd>();
  // for (auto e : es) {
  //   dele_impl<QLIB_CELL_ELEMENT_TYPE>(e, reg, macr_command, event_writer, &cmd, false);
  // }

  // DeleObjCRs* cr = new DeleObjCRs;
  // cr->Prepare(reg, es, event_writer, &cmd);

  // Command* command = new Command;
  // command->SetReceiver(cr);

  // macr_command->AddCommand(command);
}

}  // namespace qbe::undoRedoT